﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using CNStreaming;
using BobcatUtility;
using CNUtility;

namespace CNShapes
{
    /// <summary>
    /// Holds text. We hold the position of the top-left point where the
    /// text will be shown, the text itself (possibly over multiple lines)
    /// and the font information.
    /// </summary>
    public class Shape_Text : Shape
    {
        #region IStreamable methods

        /// <summary>
        /// Returns the current message version.
        /// </summary>
        public override int getMessageVersion()
        {
            return 1;
        }

        /// <summary>
        /// Serializes this class's data to the stream passed in.
        /// </summary>
        public override void toStream(MessageStream stream)
        {
            base.toStream(stream);

            stream.writePointF(m_TopLeft);
            stream.writeString(m_strFontName);
            stream.writeFloat(m_fFontSize);
            stream.writeInt(m_FontColor.ToArgb());

            // We write the collection of lines...
            stream.writeInt(m_listLines.Count);
            foreach (string s in m_listLines)
            {
                stream.writeString(s);
            }
        }

        /// <summary>
        /// Reads this object's data from the stream passed in.
        /// </summary>
        public override void fromStream(MessageStream stream, int iVersion)
        {
            base.fromStream(stream, iVersion);

            m_TopLeft = stream.readPointF();
            m_strFontName = stream.readString();
            m_fFontSize = stream.readFloat();
            m_FontColor = Color.FromArgb(stream.readInt());

            m_listLines.Clear();
            int iLines = stream.readInt();
            for (int i = 0; i < iLines; ++i)
            {
                m_listLines.Add(stream.readString());
            }

            invalidateBounds();
        }

        #endregion

        #region Shape members

        /// <summary>
        /// Returns the bounds of this shape.
        /// </summary>
        public override RectangleF getBounds(Graphics graphics)
        {
            if (m_bBoundsNeedCalculating == true)
            {
                calculateBounds(graphics);
            }
            return m_Bounds;
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Constructor.
        /// </summary>
        public Shape_Text()
        {
            // We add an initial line to the list of lines we manage...
            m_listLines.Add("");

            // We get the current font information...
            PropertyBag properties = UserPreferences.getPropertyBag();
            m_strFontName = properties.getString("DefaultFont.Name");
            m_fFontSize = properties.getFloat("DefaultFont.Size");
            m_FontColor = Color.FromArgb(properties.getInt("DefaultPen.Color"));
        }

        /// <summary>
        /// Adds a character to the current line.
        /// </summary>
        public void addChar(char c, ActionOrigin actionOrigin)
        {
            int iCurrentLine = m_listLines.Count - 1;
            m_listLines[iCurrentLine] += c;
            invalidateBounds();
            raiseShapeEditedEvent(actionOrigin);
        }

        /// <summary>
        /// Adds a new line to the collection of lines we hold.
        /// </summary>
        public void addLine(ActionOrigin actionOrigin)
        {
            m_listLines.Add("");
            invalidateBounds();
            raiseShapeEditedEvent(actionOrigin);
        }

        /// <summary>
        /// Removes a character from the current line.
        /// </summary>
        public void delete(ActionOrigin actionOrigin)
        {
            // We get the current line...
            int iCurrentLine = m_listLines.Count - 1;
            string s = m_listLines[iCurrentLine];

            // Is the line zero-length?
            if (s.Length == 0)
            {
                // The line length is zero, so we remove the current line...
                removeLine();
            }
            else
            {
                // The line is not zero length, so we delete the last character...
                s = s.Remove(s.Length - 1);
                m_listLines[iCurrentLine] = s;
            }

            invalidateBounds();
            raiseShapeEditedEvent(actionOrigin);
        }

        /// <summary>
        /// Returns the top-left point where the text is displayed.
        /// </summary>
        public PointF getTopLeft()
        {
            return m_TopLeft;
        }

        /// <summary>
        /// Sets the top-left point where the text will be displayed.
        /// </summary>
        public void setTopLeft(PointF topLeft, ActionOrigin actionOrigin)
        {
            m_TopLeft = topLeft;
            invalidateBounds();
            raiseShapeEditedEvent(actionOrigin);
        }

        /// <summary>
        /// Returns the font name.
        /// </summary>
        public string getFontName()
        {
            return m_strFontName;
        }

        /// <summary>
        /// Sets the font name.
        /// </summary>
        public void setFontName(string strFontName, ActionOrigin actionOrigin)
        {
            m_strFontName = strFontName;
            raiseShapeEditedEvent(actionOrigin);
        }

        /// <summary>
        /// Returns the font size.
        /// </summary>
        public float getFontSize()
        {
            return m_fFontSize;
        }

        /// <summary>
        /// Sets the font size.
        /// </summary>
        public void setFontSize(float fFontSize, ActionOrigin actionOrigin)
        {
            m_fFontSize = fFontSize;
            raiseShapeEditedEvent(actionOrigin);
        }

        /// <summary>
        /// Returns the font color.
        /// </summary>
        public Color getFontColor()
        {
            return m_FontColor;
        }

        /// <summary>
        /// Sets the font color.
        /// </summary>
        public void setFontColor(Color fontColor, ActionOrigin actionOrigin)
        {
            m_FontColor = fontColor;
            raiseShapeEditedEvent(actionOrigin);
        }

        /// <summary>
        /// Returns the lines of text.
        /// </summary>
        public IList<string> getLines()
        {
            return m_listLines;
        }

		/// <summary>
		/// Returns whether the shape is being edited.
		/// </summary>
		/// <returns></returns>
		public bool getIsBeingEdited()
		{
			return m_bIsBeingEdited;
		}

		/// <summary>
		/// Sets whether the shape is being edited.
		/// </summary>
		public void setIsBeingEdited(bool bIsBeingEdited, ActionOrigin actionOrigin)
		{
			m_bIsBeingEdited = bIsBeingEdited;
			raiseShapeEditedEvent(actionOrigin);
		}

        #endregion

        #region Private functions

        /// <summary>
        /// Removes the current line.
        /// </summary>
        private void removeLine()
        {
            int iCurrentLine = m_listLines.Count - 1;
            m_listLines.RemoveAt(iCurrentLine);
            if (m_listLines.Count == 0)
            {
                m_listLines.Add("");
            }
        }

        /// <summary>
        /// Sets the flag to tell us that we need to calculate the bounds.
        /// </summary>
        private void invalidateBounds()
        {
            m_bBoundsNeedCalculating = true;
        }

        /// <summary>
        /// Calculates the bounds as rendered with the graphics object
        /// passed in.
        /// </summary>
        private void calculateBounds(Graphics graphics)
        {
            // We calculate the size of each line. The bounds height is
            // the combined height of all the lines. The width is the width
            // of the longest line...
            Font font = new Font(m_strFontName, m_fFontSize);
            float fHeight = 0f;
            float fWidth = 0f;
            foreach (string s in m_listLines)
            {
                string strLine = s;
                if (strLine == "")
                {
                    // We measure a blank line if the line is empy, otherwise it
                    // will not leave space for the empty line...
                    strLine = " ";
                }
                SizeF lineSize = graphics.MeasureString(strLine, font);
                fHeight += lineSize.Height;
                fWidth = Math.Max(fWidth, lineSize.Width);
            }
            m_Bounds.X = m_TopLeft.X;
            m_Bounds.Y = m_TopLeft.Y;
            m_Bounds.Width = fWidth;
            m_Bounds.Height = fHeight;

            m_bBoundsNeedCalculating = false;
        }

        #endregion

        #region Private data

        // The point at which the text will be rendered.
        private PointF m_TopLeft = new PointF();

        // The font information...
        private string m_strFontName = "Arial";
        private float m_fFontSize = 12f;
        private Color m_FontColor = Color.Blue;

        // The lines of text...
        private IList<string> m_listLines = new List<string>();

		// The bounds...
		private bool m_bBoundsNeedCalculating = true;
        private RectangleF m_Bounds = new RectangleF();

		// Flag to tell us whether the shape is being edited...
		private bool m_bIsBeingEdited = false;

        #endregion
    
}
}
